package com.netty.service;

import com.netty.service.NettyServerHandle;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

/**
 * @author: cxt
 * @time: 2021/8/4
 * netty 服务端
 * 要启动一个Netty服务端，必须要指定三类属性，分别是线程模型、IO 模型、连接读写处理逻辑、绑定端口
 */
@Slf4j
public class NettyServer {

    /**
     * 有了Netty，你可以实现自己的HTTP服务器，FTP服务器，UDP服务器，RPC服务器，WebSocket服务器，Redis的Proxy服务器，MySQL的Proxy服务器等等。
     * 1. 创建一个ServerSocket，监听并绑定一个端口
     * 2. 一系列客户端来请求这个端口
     * 3. 服务器使用Accept，获得一个来自客户端的Socket连接对象
     * 4. 启动一个新线程处理连接
     * 4.1 读Socket，得到字节流
     * 4.2 解码协议，得到Http请求对象
     * 4.3 处理Http请求，得到一个结果，封装成一个HttpResponse对象
     * 4.4 编码协议，将结果序列化字节流 写Socket，将字节流发给客户端
     * 5. 继续循环步骤3
     */
    public void  start(Integer port) {
        NioEventLoopGroup boosGroup = new NioEventLoopGroup(); // 负责接收请求线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // 负责干活的线程组
        final AttributeKey<Object> clientKey = AttributeKey.newInstance("clientKey");
        try {
            final ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(boosGroup, workerGroup) // 1、绑定线程模型，一个负责接受请求，一个负责干活
                    .channel(NioServerSocketChannel.class) // 2、指定指定服务器的IO模型，这里使用异步的服务器端 TCP Socket 连接
//                    .handler(new ChannelHandlerAdapter() {
//                        @Override
//                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
//                            // 可选：指定服务在启动过程中的一些逻辑
//                            log.info("netty service start!");
//                            super.channelActive(ctx);
//                        }
//                    })
                    // 可选：以给服务端的 channel，也就是NioServerSocketChannel指定一些自定义属性, 然后可以通过channel.attr()取出这个属性
                    .attr(AttributeKey.newInstance("serverName"), "nettyServer")
                    // 可选：可以给每一条连接指定自定义属性
                    .childAttr(clientKey, "clientValue")
                    // 可选：服务初始化时候的设置的队列大小。【如果连接建立频繁，服务器处理创建新连接较慢，可以适当调大这个参数】
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 可选：表示是否开启TCP底层心跳机制
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 可选：表示是否开启Nagle算法【①如果要求高实时性，有数据发送时就马上发送，就关闭；②如果需要减少发送次数减少网络交互，就开启】
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 3、定义每条连接数据读写，可以设置自定业务逻辑
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                            nioSocketChannel.pipeline()
                                    .addLast("decoder", new HttpRequestDecoder())   // 解码request
                                    .addLast("encoder", new HttpResponseEncoder())  // 编码response
                                    .addLast("aggregator", new HttpObjectAggregator(512 * 1024))
                                    .addLast(new NettyServerHandle()); // 自定义逻辑处理器
                        }
                    });

            // 4.绑定端口
            ChannelFuture f = bootstrap.bind(port)
                    .addListener(future -> { // 添加一个监听器，可以监听端口是否绑定成功
                        if (future.isSuccess()) {
                            log.info("端口[{}]绑定成功!", port);
                        }
                    }).sync();
            // 关闭
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


}


